﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GDStashLib
{
  internal struct GDBlock
  {
    internal int len;
    internal int end;

  };

  public class GDStash
  {
    public GDStashSack[] sacks;

    internal static readonly int XorBitmap = 1431655765;
    internal static readonly int XorTableMultiplier = 39916801;
    internal static readonly long MaxUInt = 0x100000000L;
    private int _key;
    private int[] _table = new int[256];
    private byte[] _fileBuffer = new byte[32];
    private int _filePos;
    private string mod;
    public string FileName;


    private int get()
    {
      if (_filePos >= _fileBuffer.Length)
        throw new IOException();

      return _fileBuffer[_filePos++] & 0xFF;
    }


    private void read_key()
    {

      int k = get();
      k |= get() << 8;
      k |= get() << 16;
      k |= get() << 24;
      k ^= 0x55555555;
      _key = k;

      for (int i = 0; i < 256; i++)
      {
        k = RotateRight(k, 1);
        k *= XorTableMultiplier;
        _table[i] = k;
      }
    }

    private int next_int()
    {

      int ret = get();
      ret |= get() << 8;
      ret |= get() << 16;
      ret |= get() << 24;
      ret ^= get();

      return ret;
    }


    internal int read_int()
    {

      int k = _key;

      int val = get();
      k ^= _table[val];

      int b = get();
      k ^= _table[b];
      val |= b << 8;

      b = get();
      k ^= _table[b];
      val |= b << 16;

      b = get();
      k ^= _table[b];
      val |= b << 24;

      val ^= _key;

      _key = k;

      return val;
    }

    internal int read_byte()
    {
      int b = get();
      int ret = (b ^ _key) & 0xFF;
      _key ^= _table[b];
      return ret;
    }

    internal float read_float()
    {
      int i = read_int();
      byte[] bytes = BitConverter.GetBytes(i);
      float f = BitConverter.ToSingle(bytes, 0);
      return f;
    }

    internal string read_str()
    {
      int len = read_int();
      StringBuilder sb = new StringBuilder(len);

      for (int i = 0; i < len; i++)
      {
        sb.Append((char)read_byte());
      }
      return sb.ToString();
    }

    internal int read_block_start(GDBlock b)
    {
      int ret = read_int();
      b.len = next_int();
      b.end = _filePos + b.len;

      return ret;
    }

    internal void read_block_end(GDBlock b)
    {
      if (_filePos != b.end)
        throw new IOException();

      if (next_int() != 0)
        throw new IOException();
    }

    internal static int RotateLeft(int value, int count)
    {
      return (value << count) | (value >> (32 - count));
    }

    internal static int RotateRight(int value, int count)
    {
      return (value >> count) | (value << (32 - count));
    }


    public void Load(string filename)
    {
      
      _fileBuffer = File.ReadAllBytes(filename);
      FileName = filename;
      _filePos = 0;
      read_key();

      if (read_int() != 2)
        throw new IOException();

      GDBlock b = new GDBlock();

      if (read_block_start(b) != 18)
        throw new IOException();

      if (read_int() != 4) // version
        throw new IOException();

      if (next_int() != 0)
        throw new IOException();

      mod = read_str();

      int n = read_int();

      sacks = new GDStashSack[n];

      for (int i = 0; i < n; i++)
      {
        GDStashSack bag = new GDStashSack();
        bag.Read(this);
        sacks[i] = bag;
      }

      read_block_end(b);

    }
  }
}
